!!! info "Reference"
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
内容安全策略 (CSP) 是一个 HTTP 响应头 (HTTP Response Header)。
它的核心思想是:“只信任我告诉你的内容来源”。
作为一个网站的开发者,你可以通过发送一个名为 Content-Security-Policy/Content-Security-Policy-Report-Only 的 HTTP 头部,来告诉浏览器一个“白名单”。这个白名单精确地定义了哪些 资源(如脚本、样式表、图片、字体等)是可信的,并允许被加载和执行。
如果浏览器在页面上发现一个试图加载的资源(比如一个来自 evil-attacker.com 的脚本)不在这个白名单上,浏览器将 拒绝加载并执行 它。
主要目的:
<script> 标签,如果该脚本的来源(或其本身)不在你的 CSP 白名单中,浏览器也不会执行它。<img> 标签加载一个恶意脚本)。frame-ancestors)来控制你的网站是否可以被嵌入到其他网站的 <iframe> 中。Content-Security-Policy 头部。<script src="...">, <img src="...">, <link href="..."> 或执行内联脚本 <script>alert(1)</script>)时,它会执行以下检查:
例如: 你的网站发送了以下 CSP 头部:
Content-Security-Policy:
default-src 'self';
script-src 'self'
https://trusted.cdn.com;
img-src 'self' data:;
这表示:
'self')加载。https://trusted.cdn.com 加载。https://evil-attacker.com 的脚本,浏览器会阻止这个请求,并在控制台中记录一条 CSP 违规的警告。
previewCSP 头由一个或多个 指令(Directives) 组成,每个指令之间用分号(;)隔开.
每个指令包含一个指令名和一个或多个源值 (Source Values),源值之间用空格隔开。
基本结构:
Content-Security-Policy:
<directive-name> <source-value1> <source-value2>;
<directive-name2> <source-value3>;
指令可以分为多种:
这类指令控制着 可以加载哪些类型的资源 以及 从哪里加载。它们是 CSP 的核心,也是对抗 XSS 的主要防线。
default-src (默认源), 这是 CSP 的 基石和回退 (Fallback) 指令。
script-src, style-src 等)提供一个默认值。default-src 'self' https://trusted.com;script-src,那么浏览器就会使用 default-src 的值来限制脚本,只允许加载来自 'self' (同源) 和 https://trusted.com 的脚本。script-src (脚本源), 这是 CSP 中最关键的指令,是防止 XSS 的核心防线。
src 属性指向的 <script> 标签。<script>...</script> (默认禁止)。onclick="" (默认禁止)。eval() 和 new Function() 等 (默认禁止)。script-src 'self' https://apis.google.com 'nonce-RANDOM123';apis.google.com、以及 nonce 属性匹配的内联脚本。script-src-elem (脚本-元素)
<script> 元素。<script src="..."> (外部脚本)<script>...</script> (内联脚本块)script-src-attr (脚本-属性)
on... 事件处理器, 如onclick="...", onmouseover="..."javascript: 伪协议(例如 href="javascript:alert(1)")。<script> 元素。!!! tip
| HTML 代码示例 | 它是“元素”还是“属性”? | 优先控制它的指令 | 如果优先指令不存在,回退给谁? |
| -------------------------------------------------- | ---------------------- | ---------------- | ------------------------------ |
| A. `<script src="https:"//a.com/app.js"></script>` | 元素 (Element) | script-src-elem | script-src |
| B. `<script>alert(1);</script>` | 元素 (Element) | script-src-elem | script-src |
| C. `<button onclick="doWork();">` | 属性 (Attribute) | script-src-attr | script-src |
| D. `<div onmouseover="showTip();"></div>` | 属性 (Attribute) | script-src-attr | script-src |
| E. `<a href="javascript:alert(1)">` | 属性 (Attribute) | script-src-attr | script-src |
!!! note ""
**最重要的概念**: **回退 (Fallback) 机制** 浏览器在寻找一个资源的策略时,会按顺序检查:
1. **特定指令**: 比如加载一个脚本,浏览器会先找 `script-src`。
2. **回退到 `default-src`**: 如果 `script-src` 不存在,浏览器就会去找 `default-src`。
3. **默认允许**: 如果两者都不存在,浏览器会默认允许所有来源(即没有 CSP 保护)。
connect-src (连接源)
fetch, XHR, WebSocket)可以连接到的 URL。fetch(), XMLHttpRequest, WebSocket, EventSource。connect-src 'self' https://api.my-service.com wss://chat.my-service.com;??? Tip "script-src VS connect-src"
- **`script-src`** 是管控 **来自哪里的脚本能运行**, 在页面加载期间检查
- **`connect-src`** 是管控 **运行的代码能和哪里通信**, 在页面加载完毕, 要发请求前检查
`script-src` 就像你的厨房(网页)允许哪里来的厨师(代码)进来干活(执行), 是 "**准入许可**", 用于防止恶意代码被执行
`connect-src` 就像厨房(网页)里的厨师(脚本)允许和哪些食材供应商(连接源)通信, 是"**通信许可**", 用于防止已运行的代码将数据泄露到恶意服务器, 或从恶意服务器获取数据.
!!! example
假设你的网站 `my-website.com` 想使用谷歌分析, 但是你这么配置:
```http
Content-Security-Policy:
script-src 'self' https://www.google-analytics.com;
connect-src 'self';
```
那么:
1. **阶段一: 加载(受 `script-src` 管控)**
- 浏览器看到 `<script src="https://www.google-analytics.com/analytics.js">`。
- 浏览器检查 `script-src` 策略:`https://www.google-analytics.com` 在白名单里。
- 结果: ✅ 脚本成功加载并执行。
2. **阶段二:运行 (受 `connect-src` 管控)**
- `analytics.js` 脚本开始运行。它收集用户信息,然后尝试打包数据,通过一个 `fetch` (或 `XHR`) 请求将其发送到谷歌的收集服务器(例如 `https://stats.g.doubleclick.net` 或其他 `google.com` 的子域名)。
- 浏览器检查 `connect-src` 策略:`https://stats.g.doubleclick.net` 不在白名单里 (白名单里只有 `'self'`)。
- 结果: ❌ 网络请求被阻止。
最终结果: 谷歌分析的脚本成功 **运行** 了,但它无法 **上报** 任何数据,因此它 **实际上失效了**。
style-src (样式源)
href 属性指向的 <link rel="stylesheet">。@import 规则。<style>...</style> (默认禁止)。style 属性,如 style="color:red" (默认禁止)。style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
unsafe-inline 经常在这里被“妥协”使用,因为很多 UI 库会动态修改 style 属性。img-src (图像源)
<img>, favicon, 以及 CSS background-image 等。img-src 'self' data: https://cdn.my-images.com;
data: 允许使用 Base64 编码的内联图像。font-src (字体源)
src 属性在 CSS @font-face 规则中指定的 URL。font-src 'self' https://fonts.gstatic.com;media-src (媒体源)
<audio> 和 <video> 标签可以加载的媒体资源来源。media-src 'self' https://videos.my-cdn.com;object-src (对象源)
<object>, <embed> 和 <applet> 标签可以加载的资源来源。'none'。object-src 'none';frame-src (框架源)
<iframe> 或 <frame> 中的 URL。frame-src 'self' https://youtube.com;child-src 是一个已废弃的指令,它曾经同时控制框架 (frames) 和 Web Workers。现在它已被 frame-src 和 worker-src 取代。worker-src (Worker 源)
Worker, SharedWorker 或 ServiceWorker 加载的 URL。worker-src 'self';这类指令控制 文档(即整个页面)的属性,而不是控制加载的具体资源。它们定义了页面可以或不可以使用哪些功能。
base-uri (基础 URI)
<base> 标签是 HTML 中一个很“霸道”的标签。它会改变页面上所有 相对路径(如 href="page2.html" 或 src="img.png")的解析基准。<base> 标签(例如通过 XSS),他就可以将页面上所有相对路径的链接(比如 <script src="analytics.js">)指向他自己的恶意服务器。
<base href="https://evil-attacker.com/">。你页面上的 <script src="analytics.js"> 就会被浏览器解析为 https://evil-attacker.com/analytics.js,从而加载恶意脚本。base-uri 来锁定这个基准。base-uri 'self';
<base> 标签(如果存在)的 href 属性只能指向你自己的域名(同源)。<base> 标签,设置 base-uri 'none'; 是最安全的选择。sandbox (沙盒)
作用: 这是一个 极其强大 的指令。它在你的页面上启用一个类似于 <iframe> 的沙盒环境,极大地限制了 页面的能力。
目的: 主要用于当你需要展示用户上传的 HTML 内容(例如一个 .html 文件)时,或者当你希望对某个页面进行“降权”处理时。
工作方式: 当你发送 sandbox 指令时,它会 默认禁止 页面执行几乎所有操作,包括:
window.open 或 target="_blank")alert, confirm)如何“解禁”: 你必须通过添加 白名单值 来逐个“放行”你认为安全的功能。
示例(空值): sandbox;
示例(放行部分功能): sandbox allow-forms allow-scripts allow-same-origin;
allow-forms:允许页面提交表单。allow-scripts:允许页面执行脚本。allow-same-origin:(关键) 允许页面被视为“同源”,使其可以访问自己的 cookie、LocalStorage 等。(没有这个,页面会被视为一个唯一的、隔离的来源)。注意: sandbox 指令是 不可回退 的。它不会回退到 default-src。它要么存在,要么不存在。它通常用于你 无法完全信任的特定页面,而不是用于你的主站点。
这类指令控制 用户可以从当前页面导航到哪里,或者 谁可以导航到当前页面。
form-action (表单提交)
<form> 标签的 action 属性可以指向的 URL。action 属性指向 https://evil-attacker.com/steal-password.php。form-action 来锁定表单可以提交的目的地。form-action 'self' https://login-partner.com;
<form> 只能提交到:
'self' (你自己的域名)https://login-partner.com (例如,如果你使用了像 OAuth 这样的第三方登录服务)form-action,浏览器会回退到 default-src。frame-ancestors (框架祖先)
这是一个极其重要的、用于防止点击劫持 (Clickjacking) 的指令。
<iframe>、<frame>、<object> 或 <embed> 中。evil.com 上设置一个页面。<iframe> 将你的网站(比如 my-bank.com)整个嵌入进来。<iframe> 上层放置一个看起来无害的按钮,比如“点我赢奖品”。evil.com,以为自己点击的是“赢奖品”按钮。frame-ancestors 'none';: (最佳实践) 这告诉浏览器:“任何人都不准把我的网站嵌入到 <iframe> 中。” 这能完全杜绝点击劫持攻击。frame-ancestors 'self';: 只允许你自己的网站(同源)嵌入自己。frame-ancestors https://partner.com;: 只允许 partner.com 嵌入你的网站。⚠️ 重要区别:
frame-src (抓取指令) 控制你的页面 可以嵌入谁。frame-ancestors (导航指令) 控制 谁可以嵌入你。另外,frame-ancestors 不会回退到 default-src。它要么存在,要么不存在。它也取代了旧的、非标准的 X-Frame-Options 头部。
这类指令定义了当 CSP 策略被违反时,浏览器应该做什么。它们不会改变策略的执行,只是告诉浏览器“如果出了问题,请通知我”。
report-uri (报告 URI) - [已废弃]POST 到哪个 URL。report-uri /csp-violation-report-endpoint;report-uri。report-uri 和 report-to 以实现最大兼容性。report-to (报告至)report-uri 的 现代替代品。它旨在与新的 Reporting API 协同工作。report-to 不直接指定一个 URL,而是指定一个你在另一个 HTTP 头部 (Reporting-Endpoints) 中 预先定义好的“报告组”的名称。Reporting-Endpoints 头部) 定义所有的报告端点(不仅是 CSP,还包括证书透明度、网络错误等),然后在 CSP 策略中通过名称引用它们。??? example
第一步: 在你的响应中设置 Report-To 头部,定义一个名为 default (或任何你喜欢的名字) 的报告组。
```HTTP
Report-To: {
"group": "csp-reports",
"max_age": 10886400,
"endpoints": [
{ "url": "https://reporter.example.com/csp-endpoint" }
]
}
```
第二步: 在你的 CSP 策略中,使用 report-to 指令引用这个组名。
```HTTP
Content-Security-Policy: ...; report-to csp-reports;
```
浏览器兼容性策略 (最佳实践): 由于 report-to 还很新,你应该同时提供两者。支持 report-to 的浏览器会优先使用它;不支持的浏览器会回退到 report-uri。
```HTTP
Content-Security-Policy: ...;
report-uri /csp-report-fallback;
report-to csp-reports;
```
这组指令主要用于处理从 HTTPS 到 HTTP 的降级问题,即 “混合内容” (Mixed Content)。
什么是混合内容?
当你的主页面是通过
https安全加载的,但页面上的某些资源(如图片<img>、脚本<script>)却是通过http不安全加载的,这就叫混合内容。 现代浏览器会默认 阻止 “活动”混合内容(如http脚本),但可能仍会加载“被动”混合内容(如http图片),这会导致浏览器地址栏的安全锁 🔒 消失,并显示“不安全”警告。
upgrade-insecure-requests (升级不安全请求)
https 页面上发现任何 http:// 的请求,请在发送它之前,自动将其重写为 https://**。”upgrade-insecure-requests; (它没有值)http 到 https 时。http:// 图片链接。你不需要去数据库里修改每一条,只需要添加这个 CSP 指令,浏览器就会自动尝试用 https:// 加载它们。https 怎么办? 如果浏览器将 http://example.com/img.png 升级为 https://example.com/img.png,但 example.com 的服务器并没有配置 https 证书,那么这个请求会 失败。资源将无法加载。http:// 站点的链接(当用户点击时)重定向到 https 版本。block-all-mixed-content (阻止所有混合内容)
http:// 资源(包括图片、脚本等)在我的 https:// 页面上加载。”block-all-mixed-content;https 页面上 100% 没有不安全的内容。http 资源(即使是支持 https 的),它也会被阻止,而不是像 upgrade-insecure-requests 那样被“自动修复”。upgrade-insecure-requests 和 block-all-mixed-content,upgrade-insecure-requests 会被忽略,block-all-mixed-content 优先。upgrade-insecure-requests,它实际上已经隐含了“阻止那些无法被升级的混合内容”的意思,因此 block-all-mixed-content 在很大程度上已经被 upgrade-insecure-requests 覆盖或废弃了。upgrade-insecure-requests。